blog-banner

Santa Paws: Gift exchange sample app for dogs

Last edited on December 16, 2022

0 minute read

    ‘Tis the season for gift giving. And, if you’re like me, you enjoy getting gifts for dogs. Atticus (my tri-color corgi) gets his fair share of balls and stuffed toys (that he promptly destroys), but those are all from me. We’ve never participated in a pet-specific holiday gift exchange before, until this year.

    While Atticus doesn’t have his own Instagram account, mine is pretty much exclusively Atticus content and we follow many of his corgi friends. In a recent story, his friend Lulu invited fellow dog owners to join a gift exchange. All that’s required is for me to send a gift valued at least $10 to the assigned dog and in return, Atticus could receive anywhere between 6-36 gifts!

    I immediately opted in. What do I have to lose? I either send one gift and receive nothing, but make a dog happy, or Atticus receives many gifts, and tons of dogs are happy. Online shopping sites made this very easy.

    gift-exchange-1

    What wasn’t easy was the gift exchange process. It involved posting to your insta-story and responding to people who wanted to join, manually changing up the gift recipients, and a lot of copy paste. Because there are character limitations to Instagram DMs, the message also had to be split into 3 sends. It was tedious and there was no good way to keep track of your referrals or to have an idea of how many gifts to expect. As a software engineer, I just couldn’t stand for that. So, I built an app.

    Gift exchange app overviewCopy Icon

    The app is fairly straightforward. It needs a referral link that allows people to join the exchange (by giving credit to the person who shared it on their story), and a dashboard to provide further instructions like where to send the gift, their personal referral link, and any tracking information. This can all be accomplished using my preferred stack of Remix, Prisma, CockroachDB Serverless, and Tailwind. But before I could start coding, I had to investigate how this all worked.

    I’ve never reverse engineered a social media meme before, but the more I dug into it, the more it looked like some kind of pyramid scheme, but not that bad. Here’s how it works. You send a gift to the person who referred the person that referred you, or your grandreferrer for short. The people you refer send gifts to the person that referred you. Once the people you refer start referring people, then you start receiving gifts. Are you dizzy? Here’s a picture.

    gift-exchange-2

    You might be thinking, this makes sense, but how does it start? This was the challenge. I’m not sure how this meme began IRL, but here’s the solution I came up with: Because you need a referrer and a grandreferrer, it can’t just start with a single person. To begin a gift exchange you need at least 3 people referring each other to then start referring other people. Trust me, this works out. Here’s another picture.

    gift-exchange-3 (1)

    Gift exchange application architectureCopy Icon

    Now that we have all that figured out, let’s build the app. First, we need to decide how we are going to store all the data. We need to take into account authentication, which will be a simple email/password. We’ll need to be able to handle multiple exchanges and store all the metadata information required for each exchange like a mailing address and type of dog. This can be accomplished using the following Prisma models:

    model User {   id           String        @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid   name         String   email        String        @unique   passwordHash String        @map("password_hash")   createdAt    DateTime      @default(now()) @map("created_at") @db.Timestamptz(6)   updatedAt    DateTime      @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)   Participants Participant[]   Exchanges    Exchange[]   @@map("users") } model Exchange {   id           String        @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid   title        String   isArchived   Boolean       @default(false) @map("is_archived")   ownerId      String        @map("owner_id") @db.Uuid   createdAt    DateTime      @default(now()) @map("created_at") @db.Timestamptz(6)   updatedAt    DateTime      @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)   Owner        User          @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: NoAction)   Participants Participant[]   @@map("exchanges") } model Participant {   id         String   @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid   address1   String   address2   String?   city       String   state      String   zip        String   country    String   dogType    String   @map("dog_type")   userId     String   @map("user_id") @db.Uuid   exchangeId String   @map("exchange_id") @db.Uuid   referrerId String   @map("referrer_id") @db.Uuid   createdAt  DateTime @default(now()) @map("created_at") @db.Timestamptz(6)   updatedAt  DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)   User      User          @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction)   Exchange  Exchange      @relation(fields: [exchangeId], references: [id], onDelete: Cascade, onUpdate: NoAction)   Referrer  Participant   @relation(name: "Referrer", fields: [referrerId], references: [id], onDelete: Cascade, onUpdate: NoAction)   Referrals Participant[] @relation("Referrer")   @@unique([exchangeId, userId]) }

    One thing to mention: because we have this circular reference in the Participant model where the referrerId is another Participant, and the referrals is an array of other Participants, we need to disambiguate the relations with the @relation attribute and provide the name argument.

    If we set the provider in the schema.prisma file to cockroachdb and update our DATABASE_URL environment variable in the .env files with a CockroachDB Serverless connection string, running npx prisma db push will create the necessary tables in your database and generate the types for the Prisma Client. We’re now ready to create our Remix application.

    Application routesCopy Icon

    The application consists of the following routes:

    /invite/{referrers participantId}Copy Icon

    This is a publicly available route that can be sent to people interested in joining the exchange. It includes a summary of the exchange process and a form to sign up and join the specific exchange and set the appropriate referralId. After joining, the user will be redirected to their dashboard with further instructions.

    gift-exchange-4

    /dashboard/{users participantId}Copy Icon

    This is a password-protected route that contains information about exchanges the user is participating in. It includes instructions, information about their gift recipient, their referral link, and details about how many people they’ve referred and the estimated number of gifts they’ll receive.

    gift-exchange-5

    /Copy Icon

    This is the publicly available root of the application. It contains a log-in screen to allow people to return to their dashboard.

    gift-exchange-6

    Check out the TypeScript files under /app/routes in the project repo.

    Creating usersCopy Icon

    Now we need to create our first 3 users and exchange for them to join. Because this is a small project, I choose to bootstrap it using the Prisma seed file and save creating the new exchange function as a future enhancement. In the /prisma/seed.ts file, I was able to create 3 users and a new exchange using the Prisma Client.

    Creating the Participant records for each user was a little trickier because the referrerId wasn’t available when creating them individually because it was a reference to one of the other’s id which is created at the time insert. To get around this, I used the Prisma $rawQuery function to request 3 UUIDs from the database and used those as the ids for the Participant records.

    const [participantIds] = await db.$queryRaw<   Array<ParticipantIds> >`SELECT gen_random_uuid() AS "id1", gen_random_uuid() AS "id2", gen_random_uuid() AS "id2" `; const [participant1, participant2, participant3] = await Promise.all(   [     {       ...tmp,       id: participantIds.id1,       userId: user1.id,       referrerId: participantIds.id2,     },     {       ...tmp,       id: participantIds.id2,       userId: user2.id,       referrerId: participantIds.id3,     },     {       ...tmp,       id: participantIds.id3,       userId: user3.id,       referrerId: participantIds.id1,     },   ].map((participant) => {     return db.participant.create({ data: participant });   }) );

    Once you add the following to your package.json file, you can run this code using the npx prisma db seed command to create your first users and exchange.

    "prisma": {   "seed": "node --require esbuild-register prisma/seed.ts" }

    Fork the repoCopy Icon

    There we have it. A Secret Santa Paws gift exchange made easy. Share your referral URL on your social and wait for the gifts to come in. Not a fan of dogs? (You monster!) Fork the repo, create your own CockroachDB Serverless cluster and adapt it to your liking. I just saw a similar exchange where you send each other Whiskey instead.

    applications